package biolabs.mcl

import biolabs.mcl.syntax._

object ScalaCodeGen {

	def refer(md: ModelDefinition) =
		if(md.typeParameters.isEmpty)
			md.name
		else
			md.name + (md.typeParameters map { _.name } mkString("[", ", ", "]"))

	def refer(md: ModelDefinitionAST) =
		if(md.typeParams.isEmpty)
			md.name
		else
			md.name + (md.typeParams map { _.name } mkString("[", ", ", "]"))

	def refer(mt: MultiType): String =
		mt.types map { refer(_) } mkString " with "

	def refer(mt: MultiTypeAST): String =
		mt.types map { refer(_) } mkString " with "

	def refer(st: SingleType): String =
		st match {
			case sr: SelfRef => refer(sr)
			case at: ActualType => refer(at)
			case tv: TypeVar => refer(tv)
			case _ => throw new AssertionError("Single type other than ActualType of TypeVar: " + st)
		}

	def refer(st: SingleTypeAST): String =
		st.typeArgs match {
			case Nil => st.id
			case _ => st.id + (st.typeArgs map { refer(_) } mkString("[", ", ", "]"))
		}

	def refer(sr: SelfRef): String =
		sr match {
			// implicit reference
			case esr: ExactSelfRef =>

				// check whether the type declares a self type type parameter
				esr.declared.typeParameters.collectFirst({ case v: TypeVarSelf => v }) match {

					// yes => use the parameter's name
					case Some(v) => v.name

					// no => refer to this type by name and type arguments
					case None => referToParameterizedType(esr.name, esr.typeArguments)
				}

			// explicit reference
			case psr: ParameterizedSelfRef =>
				referToParameterizedType(psr.name, psr.typeArguments)
		}

	def refer(at: ActualType): String =
		referToParameterizedType(at.name, at.typeArguments)

	private def referToParameterizedType(name: String, typeArgs: List[MultiType]): String =
		if(typeArgs.length == 0)
			name
		else
			name + (typeArgs map { refer(_) } mkString("[", ", ", "]"))

	def refer(tv: TypeVar): String =
		tv.name

	def declare(tv: TypeVar): String =
		tv match {
			case v: TypeVarSelf =>
				// prevent a declaration of type "Self <: Self",
				// ensure a declaration of type "Self <: C[A, B, Self]"
				v.name + " <: " + referToParameterizedType(v.upperBound.name, v.upperBound.typeArguments)
			case _ =>
				tv.upperBounds match {
					case Nil => tv.name
					case list => tv.name + " <: " + (list map { refer(_) } mkString " with ")
				}
		}

	def declare(tp: TypeParamAST): String =
		tp match {
			case UnboundedTypeParamAST(name) => name
			case UpperBoundedTypeParamAST(name, ub) => name + " <: " + refer(ub)
		}

	def declare(fp: ConstructorParamAST): String =
		fp.name + ": " + refer(fp.typeSpec)

	def refer(ia: Expr): String =
		ia match {
			case VerbatimExpr(expr) =>
				expr
			case ChildSelectorExpr(model, children, variant) =>
				model.name + "." + children.name + ".variant_" + variant.mkString("_")
		}

	def extendsCode(st: SuperTypeSpec): String =
		if(st.superType.rawType.isInterface || st.initArgs.isEmpty)
			refer(st.superType)
		else
			refer(st.superType) + st.initArgs.map(refer(_)).mkString("(", ", ", ")")

	def initCode(st: SuperTypeSpec): Option[String] = {
		if(st.superType.rawType.isInterface && !st.initArgs.isEmpty)
			Some("super[" + refer(st.superType) + "].init" + st.initArgs.mkString("(", ", ", ")"))
		else
			None
	}

	def constructCode(cs: ContainerSpec): String = {
		"new " + cs.cls.getName() + "[" + (cs match {
			case _: SimpleContainerSpec => refer(cs.childType)
			case assoc: AssociativeContainerSpec => refer(cs.childType) + ", " + refer(assoc.associatedType)
		}) + "]" + cs.initArgs.mkString("(", ", ", ")")
	}

	def id(st: SingleType): String =
		st match {
			case at: ActualType => id(at)
			case tv: TypeVar => id(tv)
			case _ => throw new AssertionError("Single type other than ActualType of TypeVar: " + st)
		}

	def id(at: ActualType): String = {
		val name = at.name.replace('.', '_')
		if(at.typeArguments.length == 0)
			name
		else
			name + (at.typeArguments map { id(_) } mkString("____", "___", "____"))
	}

	def id(tv: TypeVar): String =
		tv.name

	def id(mt: MultiType): String =
		mt.types map { id(_)} mkString "__"
}